package com.wxapp.common.core.annotation.resolver;

import com.github.benmanes.caffeine.cache.Cache;
import com.wxapp.common.base.util.jackson.JacksonUtil;
import com.wxapp.common.base.util.md5.Md5Util;
import com.wxapp.common.base.util.redis.RedisUtil;
import com.wxapp.common.constant.constant.CatchCommonConstant;
import com.wxapp.common.constant.pojo.vm.ResultVm;
import com.wxapp.common.constant.properties.CaffeineProperties;
import com.wxapp.common.core.annotation.GetCatch;
import com.wxapp.common.core.annotation.util.SpELUtil;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author lgl
 * @Description
 * @Datetime 2023/9/8 11:01
 * @Modified By
 */
@Component
@Aspect
@RequiredArgsConstructor
public class GetCatchAspect {
    private final RedisUtil redisUtil;
    private final Cache<String, Object> caffeineCache;
    private final CaffeineProperties caffeineProperties;
    private final RedissonClient redissonClient;
    @Pointcut("@annotation(com.wxapp.common.core.annotation.GetCatch)")
    private void pointCut() {
    }
    /**
     * 在切入环绕通知
     */
    @Around(value = "pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        var aCatch = getCatchMethod(joinPoint);
        var annotation = aCatch.getAnnotation(GetCatch.class);
        String value = annotation.key();
        Object[] args = joinPoint.getArgs();
        if (value != null && !"".equals(value) ) {
            if (value.contains("#")){
                value = SpELUtil.parseExpression(value, aCatch, args);
            }
        }else{
            for (Object arg : args) {
                value+=Md5Util.md5(JacksonUtil.toJSONString(arg));
            }
        }
         var cache = getCache(Md5Util.md5(value), ResultVm.class);
        if (cache !=null){
            return cache;
        }
        //执行方法，获取返回值
        Object obj = joinPoint.proceed(args);
        //处理方法返回值
        if (obj !=null){
            addCache(Md5Util.md5(value),obj,annotation.expression());
        }
        return obj;
    }
    private <T> T getCache(String key, Class<T> tClass) {
        if (key == null || "".equals(key)) {
            return null;
        }
        key = CatchCommonConstant.PROJECT_CATCH + key;
        var present = caffeineCache.getIfPresent(key);
        if (present != null) {
            return (T) present;
        }
        var data = redisUtil.get(key);
        if (data == null) {
            return null;
        }
        caffeineCache.put(key, data);
        return(T) data;
    }
    private void addCache(String key, Object object, @Nullable Integer expression) {
        if (object == null || key == null || "".equals(key)) {
            return;
        }
        var lock=redissonClient.getLock(key);
        try {
            lock.lock(60, TimeUnit.SECONDS);
            caffeineCache.invalidate(key);
            removeCache(key);
            key = CatchCommonConstant.PROJECT_CATCH + key;
            caffeineCache.put(key, object);
            redisUtil.set(key, object, expression == null ? caffeineProperties.getExpire() : expression);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            try {
                lock.unlock();
            } catch (Exception e){
                lock.unlock();
            }
        }
    }

    private void removeCache(String key) {
        if (key == null || "".equals(key)) {
            return;
        }
        key = CatchCommonConstant.PROJECT_CATCH + key;
        redisUtil.del(key);
    }

    private Method getCatchMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        //处理方法参数，如果是User就进行加密处理
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return method;
    }
}
